package com.erlang4j.internal.messages;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangList;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangTuple;
import com.erlang4j.api.Erlang4jMessageLanguage;
import com.erlang4j.api.IBinding;
import com.erlang4j.api.exceptions.Erlang4jKeyNotFoundException;
import com.erlang4j.api.exceptions.Erlang4jMissingParameterException;

/**
 * <p>
 * See the interface for details on this class. Please don't construct it directly, go through the IErlang4J interface
 * </p>
 * 
 * @author Phil Rice
 */
public class MessageComposer implements IMessageComposer {

	private final IBinding defaultBindings;

	public MessageComposer(IBinding defaultBindings) {
		this.defaultBindings = defaultBindings;
	}

	public OtpErlangObject composeFromBinding(String input, IBinding binding) {
		Iterator<MessageToken> iterator = new MessageLexicalAnalyser(input).iterator();
		OtpErlangObject result = processToken(binding.merge(defaultBindings), iterator.next(), iterator);
		return result;
	}

	public OtpErlangObject compose(String input, Object... parameters) {
		int i = 0;
		IBinding binding = new Binding();
		for (Object object : parameters) {
			String key = "P" + i;
			OtpErlangObject otpErlangObject = Erlang4jMessageLanguage.make(i, object);
			binding = binding.bind(key, otpErlangObject);
			i++;
		}
		return composeFromBinding(input, binding);
	}

	private OtpErlangObject processToken(IBinding binding, MessageToken token, Iterator<MessageToken> iterator) {
		switch (token.type) {
		case atomName:
			OtpErlangAtom atom = new OtpErlangAtom(token.value);
			return atom;
		case variable:
			try {
				OtpErlangObject result = binding.getRaw(token.value);
				return result;
			} catch (Erlang4jKeyNotFoundException e) {
				throw new Erlang4jMissingParameterException(token.value);
			}
		case openTuple:
			return processTuple(binding, iterator);
		case openList:
			return processList(binding, iterator);
		default:
			throw new IllegalStateException(token + "Should not be able to get here because of validation in the lexical analysed");
		}
	}

	private OtpErlangList processList(IBinding binding, Iterator<MessageToken> iterator) {
		return new OtpErlangList(processListOrTuple(binding, iterator, MessageTokenType.closeList));
	}

	private OtpErlangTuple processTuple(IBinding binding, Iterator<MessageToken> iterator) {
		return new OtpErlangTuple(processListOrTuple(binding, iterator, MessageTokenType.closeTuple));

	}

	private OtpErlangObject[] processListOrTuple(IBinding binding, Iterator<MessageToken> iterator, MessageTokenType closing) {
		List<OtpErlangObject> result = new ArrayList<OtpErlangObject>();
		while (true) {
			MessageToken token = iterator.next();
			switch (token.type) {
			case closeList:
			case closeTuple:
				if (token.type == closing)
					return result.toArray(new OtpErlangObject[0]);
				else
					throw new IllegalStateException("Should not be able to get here because of validation in the lexical analysed");
			default:
				result.add(processToken(binding, token, iterator));
			}
		}
	}

}